/**
 * created by 小卷毛, 2019-09-17 017
 * Copyright (c) 2019, 416143467@qq.com All Rights Reserved.
 * #                   *********                            #
 * #                  ************                          #
 * #                  *************                         #
 * #                 **  ***********                        #
 * #                ***  ****** *****                       #
 * #                *** *******   ****                      #
 * #               ***  ********** ****                     #
 * #              ****  *********** ****                    #
 * #            *****   ***********  *****                  #
 * #           ******   *** ********   *****                #
 * #           *****   ***   ********   ******              #
 * #          ******   ***  ***********   ******            #
 * #         ******   **** **************  ******           #
 * #        *******  ********************* *******          #
 * #        *******  ******************************         #
 * #       *******  ****** ***************** *******        #
 * #       *******  ****** ****** *********   ******        #
 * #       *******    **  ******   ******     ******        #
 * #       *******        ******    *****     *****         #
 * #        ******        *****     *****     ****          #
 * #         *****        ****      *****     ***           #
 * #          *****       ***        ***      *             #
 * #            **       ****        ****                   #
 */
package com.af.community.smart.utils

import android.annotation.SuppressLint
import android.content.Context
import com.af.community.smart.share.OSSConst
import com.alibaba.sdk.android.oss.ClientConfiguration
import com.alibaba.sdk.android.oss.ClientException
import com.alibaba.sdk.android.oss.OSSClient
import com.alibaba.sdk.android.oss.ServiceException
import com.alibaba.sdk.android.oss.common.OSSLog
import com.alibaba.sdk.android.oss.common.auth.OSSStsTokenCredentialProvider
import com.alibaba.sdk.android.oss.model.PutObjectRequest
import com.alibaba.sdk.android.oss.model.PutObjectResult
import com.lzg.okrx.adapter.SingleBody
import com.lzy.okgo.OkGo
import com.lzy.okgo.convert.StringConvert
import com.lzy.okgo.utils.OkLogger
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.rxkotlin.subscribeBy
import io.reactivex.rxkotlin.toObservable
import io.reactivex.schedulers.Schedulers
import org.json.JSONObject
import java.util.concurrent.TimeUnit

class OSSHelper private constructor() {

    private var mClient: OSSClient? = null

    /**
     * OSS初始化，过期自动更新Token
     */
    @SuppressLint("CheckResult")
    fun initOSS(
        context: Context,
        endpoint: String = OSSConst.OSS_ENDPOINT,
        server: String = OSSConst.STS_SERVER_URL,
        connect: Int = 15 * 1000,
        socket: Int = 15 * 1000,
        max: Int = 5,
        error: Int = 2
    ) {
        OSSLog.enableLog()
        Completable.timer(100, TimeUnit.MILLISECONDS)
            .subscribeOn(Schedulers.io())
            .observeOn(Schedulers.io())
            .subscribe {
                //需要在子线程中创建对象
                mClient = OSSClient(
                    context,
                    endpoint,
                    OSSAuthCredentialsProvider(server),
                    ClientConfiguration().apply {
                        connectionTimeout = connect //连接超时，默认15秒
                        socketTimeout = socket      //socket超时，默认15秒
                        maxConcurrentRequest = max  //最大并发请求书，默认5个
                        maxErrorRetry = error       //失败后最大重试次数，默认2次
                    }
                )
            }
    }

    /**
     * OSS初始化，手动更新Token
     */
    @SuppressLint("CheckResult")
    fun initOSSHand(
        context: Context,
        endpoint: String = OSSConst.OSS_ENDPOINT,
        server: String = OSSConst.STS_SERVER_URL,
        connect: Int = 15 * 1000,
        socket: Int = 15 * 1000,
        max: Int = 5,
        error: Int = 2,
        onFinish: (Boolean) -> Unit = {}
    ) {
        OkGo.post<String>(server)
            .converter(StringConvert())
            .adapt(SingleBody<String>())
            .observeOn(Schedulers.io())
            .map {
                val jsonObj = JSONObject(it)
                if (jsonObj.has("AccessKeyId")
                    && jsonObj.has("AccessKeySecret")
                    && jsonObj.has("SecurityToken")
                ) {
                    OSSLog.enableLog()
                    val credentialProvider = OSSStsTokenCredentialProvider(
                        jsonObj.optString("AccessKeyId"),
                        jsonObj.optString("AccessKeySecret"),
                        jsonObj.optString("SecurityToken")
                    )

                    mClient = OSSClient(
                        context,
                        endpoint,
                        credentialProvider,
                        ClientConfiguration().apply {
                            connectionTimeout = connect //连接超时，默认15秒
                            socketTimeout = socket      //socket超时，默认15秒
                            maxConcurrentRequest = max  //最大并发请求书，默认5个
                            maxErrorRetry = error       //失败后最大重试次数，默认2次
                        }
                    )
                }
                return@map mClient != null
            }
            .compose(applySchedulers())
            .subscribeBy(onSuccess = onFinish)
    }

    /**
     * OSS上传单个文件
     */
    @SuppressLint("CheckResult")
    fun uploadImage(
        path: String = "",
        onBefore: () -> Unit = {},
        onAfter: () -> Unit = {},
        onProgress: (PutObjectRequest, Long, Long) -> Unit = { _, _, _ -> },
        onSuccess: (PutObjectResult) -> Unit = {},
        OnError: (Exception) -> Unit = {}
    ) {

        requireNotNull(mClient) { "OSSClient is not initialized, please call the init method first!" }
        require(path.isNotEmpty()) { "path cannot be empty!" }

        Single.just(path)
            .map {
                //上传的图片根据时间进行分组
                val timeFolder = TimeHelper.getInstance().getNowTime("yyyy-MM/dd/")
                val fileName = "picture_${System.currentTimeMillis()}.jpg"
                val mRequest = PutObjectRequest(
                    OSSConst.BUCKET_NAME,
                    "community/${timeFolder}${fileName}",
                    it
                )

                mRequest.setProgressCallback { request, currentSize, totalSize ->
                    onProgress.invoke(request, currentSize, totalSize)
                }

                try {
                    return@map mClient!!.putObject(mRequest)
                        .apply {
                            responseHeader["url"] = OSSConst.SERVER_URL + timeFolder + fileName
                        }
                } catch (exception: ClientException) {
                    //本地异常，如网络异常等
                    OkLogger.e(exception.message)

                    OnError.invoke(exception)
                } catch (exception: ServiceException) {
                    //服务异常
                    OkLogger.e("ErrorCode:${exception.errorCode}")
                    OkLogger.e("RequestId:${exception.requestId}")
                    OkLogger.e("HostId:${exception.hostId}")
                    OkLogger.e("RawMessage:${exception.rawMessage}")

                    OnError.invoke(exception)
                }
                return@map null
            }
            .compose(applySchedulers())
            .doOnSubscribe { onBefore.invoke() }
            .doFinally { onAfter.invoke() }
            .subscribe(
                {
                    if (it != null) onSuccess.invoke(it)
                    else OkLogger.e("Oss failed to upload pictures!")
                },
                {
                    OkLogger.e(it.message)
                    OnError.invoke(it as Exception)
                }
            )
    }

    /**
     * OSS上传多个文件
     */
    @SuppressLint("CheckResult")
    fun uploadImages(
        pathList: List<String> = ArrayList(),
        onBefore: () -> Unit = {},
        onAfter: () -> Unit = {},
        onProgress: (Int, PutObjectRequest, Long, Long) -> Unit = { _, _, _, _ -> },
        onSuccess: (Int, PutObjectResult?) -> Unit = { _, _ -> },
        OnError: (Int, Exception) -> Unit = { _, _ -> }
    ) {
        requireNotNull(mClient) { "OSSClient is not initialized, please call the init method first!" }
        require(pathList.isNotEmpty()) { "pathList cannot be empty!" }

        pathList.toObservable()
            .concatMap { Observable.just(it) }
            .map {
                //上传的图片根据时间进行分组
                val timeFolder = TimeHelper.getInstance().getNowTime("yyyy-MM/dd/")
                val fileName = "picture_${System.currentTimeMillis()}.jpg"
                val mRequest = PutObjectRequest(
                    OSSConst.BUCKET_NAME,
                    "community/${timeFolder}${fileName}",
                    it
                )

                mRequest.setProgressCallback { request, currentSize, totalSize ->
                    onProgress(pathList.indexOf(it), request, currentSize, totalSize)
                }

                try {
                    return@map mClient!!.putObject(mRequest)
                        .apply {
                            eTag = pathList.indexOf(it).toString()
                            responseHeader["url"] = OSSConst.SERVER_URL + timeFolder + fileName
                        }
                } catch (exception: ClientException) {
                    //本地异常，如网络异常等
                    OkLogger.e(exception.message)

                    OnError(pathList.indexOf(it), exception)
                } catch (exception: ServiceException) {
                    //服务异常
                    OkLogger.e("ErrorCode:${exception.errorCode}")
                    OkLogger.e("RequestId:${exception.requestId}")
                    OkLogger.e("HostId:${exception.hostId}")
                    OkLogger.e("RawMessage:${exception.rawMessage}")

                    OnError(pathList.indexOf(it), exception)
                }
                return@map null
            }
            .compose(applyObservableSchedulers())
            .doOnSubscribe { onBefore() }
            .doFinally { onAfter() }
            .subscribe(
                {
                    it?.let { onSuccess((it.eTag).toInt(), it) }
                },
                {
                    OkLogger.e(it.message)
                    OnError(-1, it as Exception)
                },
                { onSuccess(pathList.size, null) }
            )
    }

    companion object {
        private var instance: OSSHelper? = null

        @JvmStatic
        val manager: OSSHelper
            get() {
                if (instance == null) instance = OSSHelper()
                return instance!!
            }
    }

}